import React from 'react'
import { Spin } from 'antd'
import G6 from '@antv/g6'
import { getHashParameterByName } from '@/utils/helper'
import { getLineage } from './api'
import styles from './index.less'

const {
  Util,
  registerBehavior,
  registerEdge,
  registerNode,
} = G6

const isInBBox = (point, bbox) => {
  const {
    x,
    y,
  } = point
  const {
    minX,
    minY,
    maxX,
    maxY,
  } = bbox

  return x < maxX && x > minX && y > minY && y < maxY
}

const calBoxColor = (label) => {
  if (label.indexOf('INSERT-SELECT') > -1) {
    return '#b24b78'
  }
  if (label.indexOf('RS-') > -1) {
    return '#d26b58'
  }
  if (label.indexOf('result_of_') > -1) {
    return '#d26b58'
  }
  return '#91c051'
}

const itemHeight = 20

export default class BloodRelationshipMap extends React.PureComponent {
  constructor(props) {
    super(props)
    this.state = {
      loading: false,
      tables: [],
      relationships: [],
    }
    this.mapRef = React.createRef()
  }

  componentDidMount() {
    this.setState({ loading: true })
    getLineage(this.props.obj).then(
      (res) => {
        this.setState({ loading: false })
        if (res[0]) {
          const mockObj = JSON.parse(res[0])
          const tables = mockObj.data?.graph?.elements?.tables || []
          const relationships = mockObj.data?.graph?.elements?.edges || []
          this.setState({ tables, relationships })
          this.initData(tables, relationships)
        }
      },
    )
    this.iniDarwComponent()
  }

  UNSAFE_componentWillReceiveProps(nextProps) {
    if (nextProps.height !== this.props.height) {
      const { tables, relationships } = this.state
      if (tables?.length && relationships) {
        this.graph && this.graph.destroy()
        this.initData(tables, relationships, nextProps.height)
      }
    }
  }

  componentWillUnmount() {
    this.graph && this.graph.destroy()
  }

  iniDarwComponent = () => {
    registerBehavior('dice-er-scroll', {
      getDefaultCfg() {
        return {
          multiple: true,
        }
      },
      getEvents() {
        return {
          itemHeight,
          wheel: 'scorll',
          click: 'click',
          'node:mousemove': 'move',
          'node:mousedown': 'mousedown',
          'node:mouseup': 'mouseup',
        }
      },
      scorll(e) {
        // e.preventDefault()
        // const {
        //   graph,
        // } = this
        // const nodes = graph.getNodes().filter((n) => {
        //   const bbox = n.getBBox()

        //   return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox)
        // })

        // const x = e.deltaX || e.movementX
        // let y = e.deltaY || e.movementY
        // if (!y && navigator.userAgent.indexOf('Firefox') > -1) y = (-e.wheelDelta * 125) / 3

        // if (nodes) {
        //   const edgesToUpdate = new Set()
        //   nodes.forEach((node) => {
        //     const model = node.getModel()
        //     if (model.attrs.length < 2) {
        //       return
        //     }
        //     const idx = model.startIndex || 0
        //     let startX = model.startX || 0.5
        //     let startIndex = idx + y * 0.02
        //     startX -= x
        //     if (startIndex < 0) {
        //       startIndex = 0
        //     }
        //     if (startX > 0) {
        //       startX = 0
        //     }
        //     if (startIndex > model.attrs.length - 1) {
        //       startIndex = model.attrs.length - 1
        //     }
        //     graph.updateItem(node, {
        //       startIndex,
        //       startX,
        //     })
        //     node.getEdges().forEach(edge => edgesToUpdate.add(edge))
        //   })
        //   // G6 update the related edges when graph.updateItem with a node according to the new properties
        //   // here you need to update the related edges manualy since the new properties { startIndex, startX } for the nodes are custom, and cannot be recognized by G6
        //   edgesToUpdate.forEach(edge => edge.refresh())
        // }
      },
      click(e) {
        const {
          graph,
        } = this
        const item = e.item
        const shape = e.shape
        if (!item) {
          return
        }

        if (shape.get('name') === 'collapse') {
          graph.updateItem(item, {
            collapsed: true,
            size: [300, 50],
          })
          setTimeout(() => graph.layout(), 100)
        } else if (shape.get('name') === 'expand') {
          graph.updateItem(item, {
            collapsed: false,
            size: [300, 80],
          })
          setTimeout(() => graph.layout(), 100)
        }

        const name = e.shape.get('name')

        const setSourceRelated = (sourceEdge) => {
          if (sourceEdge) {
            this.graph.updateItem(sourceEdge, { selectedIndex: 'true' })
            const targetNode = sourceEdge.getTarget()
            const targetAttrs = targetNode.getModel().attrs
            const relatedArr = []
            targetAttrs.forEach((_node, index) => {
              if (_node.key === sourceEdge.getModel().targetKey) {
                relatedArr.push(index)
              }
            })
            if (relatedArr.length > 0) {
              this.graph.updateItem(targetNode, { relatedIndex: relatedArr })
              const tempEdges = targetNode.getEdges()
              tempEdges.forEach((_edge) => {
                if ((_edge.getModel().sourceKey === sourceEdge.getModel().targetKey) && (_edge.getModel().source === targetNode.getModel().id)) {
                  setSourceRelated(_edge)
                }
              })
            }
          }
        }

        const setTaegetRelated = (targetEdge) => {
          if (targetEdge) {
            this.graph.updateItem(targetEdge, { selectedIndex: 'true' })
            const sourceNode = targetEdge.getSource()
            const sourceAttrs = sourceNode.getModel().attrs
            const relatedArr = []
            sourceAttrs.forEach((_node, index) => {
              if (_node.key === targetEdge.getModel().sourceKey) {
                relatedArr.push(index)
              }
            })
            if (relatedArr.length > 0) {
              this.graph.updateItem(sourceNode, { relatedIndex: relatedArr })
              const tempEdges = sourceNode.getEdges()
              tempEdges.forEach((_edge) => {
                if ((_edge.getModel().targetKey === targetEdge.getModel().sourceKey) && (_edge.getModel().target === sourceNode.getModel().id)) {
                  setTaegetRelated(_edge)
                }
              })
            }
          }
        }

        const edges = item.getEdges()
        if (name && name.startsWith('item')) {
          this.graph.getNodes().forEach(node => {
            this.graph.updateItem(node, { selectedIndex: NaN, relatedIndex: [] })
          })
          this.graph.getEdges().forEach(edge => { this.graph.updateItem(edge, { selectedIndex: null }) })
          this.graph.updateItem(item, { selectedIndex: Number(name.split('-')[1]) })
          edges.forEach((_edge) => {
            if ((_edge.getModel().sourceKey === name.split('-')[2]) && (item.getModel().id === _edge.getModel().source)) {
              setSourceRelated(_edge)
            }
            if ((_edge.getModel().targetKey === name.split('-')[2]) && (item.getModel().id === _edge.getModel().target)) {
              setTaegetRelated(_edge)
            }
          })
        } else {
          this.graph.updateItem(item, {
            selectedIndex: NaN,
          })
          edges.forEach((_edge) => this.graph.updateItem(_edge, { selectedIndex: null }))
        }
      },
      mousedown(e) {
        // this.isMousedown = true
      },
      mouseup(e) {
        // this.isMousedown = false
      },
      move(e) {
        // if (this.isMousedown) return
      },
    })

    registerEdge('dice-er-edge', {
      draw(cfg, group) {
        const edge = group.cfg.item
        const sourceNode = edge.getSource().getModel()
        const targetNode = edge.getTarget().getModel()

        const sourceIndex = sourceNode.attrs.findIndex(
          (e) => e.key === cfg.sourceKey,
        )

        const sourceStartIndex = sourceNode.startIndex || 0

        let sourceY = 15

        if (!sourceNode.collapsed && sourceIndex > sourceStartIndex - 1) {
          sourceY = 30 + (sourceIndex - sourceStartIndex + 0.5) * itemHeight
          sourceY = sourceY
        }

        const targetIndex = targetNode.attrs.findIndex(
          (e) => e.key === cfg.targetKey,
        )

        const targetStartIndex = targetNode.startIndex || 0

        let targetY = 15

        if (!targetNode.collapsed && targetIndex > targetStartIndex - 1) {
          targetY = (targetIndex - targetStartIndex + 0.5) * itemHeight + 30
          targetY = targetY
        }

        const startPoint = {
          ...cfg.startPoint,
        }
        const endPoint = {
          ...cfg.endPoint,
        }

        startPoint.y += sourceY
        endPoint.y += targetY

        let shape
        if (sourceNode.id !== targetNode.id) {
          shape = group.addShape('path', {
            attrs: {
              stroke: cfg.selectedIndex ? '#000' : '#ababab',
              path: [
                ['M', startPoint.x, startPoint.y],
                [
                  'C',
                  endPoint.x / 3 + (2 / 3) * startPoint.x,
                  startPoint.y,
                  endPoint.x / 3 + (2 / 3) * startPoint.x,
                  endPoint.y,
                  endPoint.x,
                  endPoint.y,
                ],
              ],
              endArrow: true,
            },
            // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
            name: 'path-shape',
          })
        } else if (!sourceNode.collapsed) {
          let gap = Math.abs((startPoint.y - endPoint.y) / 3)
          if (startPoint.index === 1) {
            gap = -gap
          }
          shape = group.addShape('path', {
            attrs: {
              stroke: cfg.selectedIndex ? '#000' : '#ababab',
              path: [
                ['M', startPoint.x, startPoint.y],
                [
                  'C',
                  startPoint.x - gap,
                  startPoint.y,
                  startPoint.x - gap,
                  endPoint.y,
                  startPoint.x,
                  endPoint.y,
                ],
              ],
              endArrow: true,
            },
            // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
            name: 'path-shape',
          })
        }

        return shape
      },
      afterDraw(cfg, group) {
        const labelCfg = cfg.labelCfg || {}
        const edge = group.cfg.item
        const sourceNode = edge.getSource().getModel()
        const targetNode = edge.getTarget().getModel()
        if (sourceNode.collapsed && targetNode.collapsed) {
          return
        }
        const path = group.find(
          (element) => element.get('name') === 'path-shape',
        )

        const labelStyle = Util.getLabelPosition(path, 0.5, 0.5, -1, true)
        const label = group.addShape('text', {
          attrs: {
            ...labelStyle,
            text: cfg.label || '',
            fill: '#5B8FF9',
            textAlign: 'center',
            stroke: '#fff',
            lineWidth: 1,
          },
        })
        label.hide()
        label.rotateAtStart(labelStyle.rotate)
      },
      setState(name, value, item) {
        const group = item.getContainer()
        const text = group.get('children')[1] // 顺序根据 draw 时确定
        if (name === 'active' && text) {
          if (value) {
            text.show()
          } else {
            text.hide()
          }
        }
      },
    })

    registerNode('dice-er-box', {
      draw(cfg, group) {
        const width = 250
        const itemCount = cfg.attrs.length
        const height = cfg.attrs.length * itemHeight + 30
        const boxStyle = {
          stroke: calBoxColor(cfg.label),
          radius: 4,
        }

        const {
          attrs = [],
            startIndex = 0,
            selectedIndex,
            relatedIndex,
            collapsed,
            icon,
        } = cfg
        const list = attrs
        const afterList = list.slice(
          Math.floor(startIndex),
          Math.floor(startIndex + itemCount),
        )
        const offsetY = (0.5 - (startIndex % 1)) * itemHeight + 30

        group.addShape('rect', {
          attrs: {
            fill: boxStyle.stroke,
            height: 30,
            width,
            radius: [boxStyle.radius, boxStyle.radius, 0, 0],
          },
          draggable: true,
        })

        let fontLeft = 12

        if (icon && icon.show !== false) {
          group.addShape('image', {
            attrs: {
              x: 8,
              y: 8,
              height: 16,
              width: 16,
              ...icon,
            },
          })
          fontLeft += 18
        }

        group.addShape('text', {
          attrs: {
            y: 22,
            x: fontLeft,
            fill: '#fff',
            text: cfg.label,
            fontSize: 12,
            fontWeight: 500,
          },
        })

        // group.addShape('rect', {
        //   attrs: {
        //     x: 0,
        //     y: collapsed ? 30 : height - 15,
        //     height: 15,
        //     width,
        //     fill: '#eee',
        //     radius: [0, 0, boxStyle.radius, boxStyle.radius],
        //     cursor: 'pointer',
        //   },
        //   // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
        //   name: collapsed ? 'expand' : 'collapse',
        // })

        // group.addShape('text', {
        //   attrs: {
        //     x: width / 2 - 6,
        //     y: (collapsed ? 30 : height - 15) + 12,
        //     text: collapsed ? '+' : '-',
        //     width,
        //     fill: '#000',
        //     radius: [0, 0, boxStyle.radius, boxStyle.radius],
        //     cursor: 'pointer',
        //   },
        //   // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
        //   name: collapsed ? 'expand' : 'collapse',
        // })

        const keyshape = group.addShape('rect', {
          attrs: {
            x: 0,
            y: 0,
            width,
            height: collapsed ? 45 : height,
            ...boxStyle,
          },
          draggable: true,
        })

        if (collapsed) {
          return keyshape
        }

        const listContainer = group.addGroup({})
        listContainer.setClip({
          type: 'rect',
          attrs: {
            x: -8,
            y: 30,
            width: width + 16,
            height: height - 30,
            fill: '#555',
          },
        })
        listContainer.addShape({
          type: 'rect',
          attrs: {
            x: 1,
            y: 30,
            width: width - 2,
            height: height - 30,
          },
          draggable: true,
        })

        if (list.length > itemCount) {
          const barStyle = {
            width: 4,
            padding: 0,
            boxStyle: {
              stroke: '#00000022',
            },
            innerStyle: {
              fill: '#00000022',
            },
          }

          listContainer.addShape('rect', {
            attrs: {
              y: 30,
              x: width - barStyle.padding - barStyle.width,
              width: barStyle.width,
              height,
              ...barStyle.boxStyle,
              fill: '#aaa',
            },
          })

          const indexHeight = afterList.length > itemCount
            ? (afterList.length / list.length) * height
            : 10

          listContainer.addShape('rect', {
            attrs: {
              y: 30
                + barStyle.padding
                + (startIndex / list.length) * (height - 30),
              x: width - barStyle.padding - barStyle.width,
              width: barStyle.width,
              height: Math.min(height, indexHeight),
              ...barStyle.innerStyle,
            },
          })
        }
        if (afterList) {
          afterList.forEach((e, i) => {
            const isSelected = Math.floor(startIndex) + i === Number(selectedIndex)
            const isRelated = (relatedIndex || []).includes(Math.floor(startIndex) + i)
            const getColor = () => {
              if (isSelected) {
                return '#faebd7'
              }
              if (isRelated) {
                return '#ddd'
              }
              return 'white'
            }
            const { key = '', label } = e
            let keystr = ''
            if (label) {
              keystr = label
            }
            const labelStr = keystr.length > 26 ? `${keystr.slice(0, 24) }...` : keystr

            listContainer.addShape('rect', {
              attrs: {
                x: 1,
                y: i * itemHeight - itemHeight / 2 + offsetY,
                width: width - 4,
                height: itemHeight,
                radius: 2,
                lineWidth: 1,
                cursor: 'pointer',
                fill: getColor(),
              },
              // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
              name: `item-${Math.floor(startIndex) + i}-${key}-content`,
              draggable: true,
            })

            if (!cfg.hideDot) {
              listContainer.addShape('circle', {
                attrs: {
                  x: 0,
                  y: i * itemHeight + offsetY,
                  r: 3,
                  stroke: boxStyle.stroke,
                  fill: 'white',
                  radius: 2,
                  lineWidth: 1,
                  cursor: 'pointer',
                },
              })
              listContainer.addShape('circle', {
                attrs: {
                  x: width,
                  y: i * itemHeight + offsetY,
                  r: 3,
                  stroke: boxStyle.stroke,
                  fill: 'white',
                  radius: 2,
                  lineWidth: 1,
                  cursor: 'pointer',
                },
              })
            }

            listContainer.addShape('text', {
              attrs: {
                x: 12,
                y: i * itemHeight + offsetY + 6,
                text: labelStr,
                fontSize: 12,
                fill: '#000',
                fontFamily: 'Avenir,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol',
                full: e,
                fontWeight: (isSelected || isRelated) ? 500 : 100,
                cursor: 'pointer',
              },
              // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
              name: `item-${Math.floor(startIndex) + i}-${key}`,
            })
          })
        }

        return keyshape
      },
      getAnchorPoints() {
        return [
          [0, 0],
          [1, 0],
        ]
      },
    })
  }

  initData = (tables, relationships, _height) => {
    const dataTransform = () => {
      const nodes = []
      const edges = []
      tables.map((node) => {
        nodes.push({
          hideDot: true,
          id: node.id,
          label: node.label.content,
          attrs: (node.columns || []).map((column) => ({ key: column.id, label: column.label.content })),
        })
      })
      relationships.map((edge) => {
        edges.push({
          source: edge.sourceId.split('::')[0],
          sourceKey: edge.sourceId,
          target: edge.targetId.split('::')[0],
          targetKey: edge.targetId,
          // label: edge.columns,
        })
      })
      // console.log(nodes)
      // console.log(edges)

      return {
        nodes,
        edges,
      }
    }

    setTimeout(() => {
      const container = this.mapRef.current
      const width = container.scrollWidth
      const height = (_height || this.props.height || 700) - 20
      this.graph = new G6.Graph({
        container: this.mapRef.current,
        width,
        height,
        defaultNode: {
          size: [300, 200],
          type: 'dice-er-box',
          color: '#5B8FF9',
          style: {
            fill: '#9EC9FF',
            lineWidth: 3,
          },
          labelCfg: {
            style: {
              fill: 'black',
              fontSize: 20,
            },
          },
        },
        defaultEdge: {
          type: 'dice-er-edge',
          style: {
            stroke: '#e2e2e2',
            lineWidth: 4,
            endArrow: true,
          },
        },
        layout: {
          type: 'dagre',
          rankdir: 'LR',
          align: 'UL',
          controlPoints: true,
          nodesepFunc: () => 0.2,
          ranksepFunc: () => 0.5,
        },
        modes: {
          default: ['dice-er-scroll', 'drag-node', 'drag-canvas', 'zoom-canvas'],
        },
        animate: true,
        fitView: true,
      })

      this.graph.on('edge:mouseenter', (ev) => {
        const edge = ev.item
        this.graph.setItemState(edge, 'active', true)
      })

      this.graph.on('edge:mouseleave', (ev) => {
        const edge = ev.item
        this.graph.setItemState(edge, 'active', false)
      })

      this.graph.data(dataTransform())

      this.graph.render()
    }, 100)
  }

  render() {
    return <Spin spinning={this.state.loading}><div className={styles.blood_map} ref={this.mapRef} style={{ height: `${this.props.height}px` }} /></Spin>
  }
}
